package com.meshnetics.gb.type.impl.additional;

import com.meshnetics.gb.util.Utils;
import com.meshnetics.gb.util.checkers.RangeCheck;
import com.meshnetics.gb.util.formatters.NumberFormatter;

import java.nio.ByteBuffer;

/**
 * Base class for number (long castable) types used in ZigBee stack. Implements
 * Byteable interface for simple storing to byte buffer, implements Comparable
 * interface as all original Number-s. <b>NOTE</b> if subclass return null
 * RangeCheck-er or null NumberFormatter than uncommented NullPointerException
 * will be thrown.
 *
 * @author Yury Aserchev (yaserchev@meshnetics.com)
 *         Date: 07.07.2008 18:56:21
 */
public abstract class ByteableNumber extends BaseDataValue implements Comparable {
    /** Minimal supported by ByteableNumber size in bytes. */
    private static final int MIN_SIZE = 1;
    /** Maximal supported by ByteableNumber size in bytes. */
    private static final int MAX_SIZE = 8;
    /** Size of ByteableNumber in bytes. */
    private final int size;
    /** Value of ByteableNumber as long. */
    private final long value;
    /** Cached hash code value. */
    private final int cachedHash;
    /** Cached toString representation. */
    private final String cachedString;

    /**
     * Internal constructor. It initializes size and value fields by passed
     * parameters. If useBuff flag is true, than value parameter is ignored
     * and value field is set from byte buffer. Otherwise value is set from
     * passed value parameter. After value field is set up RangeCheck-er
     * called to check if field has valid value.
     * @param type type of the number.
     * @param size size of the number in bytes.
     * @param value value as long.
     * @param buffer byte buffer.
     * @param useBuff flag indicating that value must be read from byte buffer.
     * @throws NullPointerException if buffer parameter is null when useBuff
     * flag is true.
     * @throws IllegalArgumentException if size is less than 1 or greater
     * than 8; if value cannot be read from byte buffer when useBuff flag is
     * true; if value is out of range.
     */
    private ByteableNumber(final TypeInstantiable type, final int size, final long value,
                           final ByteBuffer buffer, final boolean useBuff) {
        super(type);
        if (size < MIN_SIZE) {
            throw new IllegalArgumentException("size " + size + " is less than minimal " + MIN_SIZE);
        } else if (size > MAX_SIZE) {
            throw new IllegalArgumentException("size " + size + " is greater than maximal " + MAX_SIZE);
        }
        this.size = size;
        if (useBuff) {
            Utils.checkBuffer(buffer, this.size);
            this.value = readBytes(buffer);
        } else {
            this.value = value;
        }
        getRangeCheck().checkRange(this.value);
        cachedHash = 29 * this.size + (int) (this.value ^ (this.value >>> 29));
        cachedString = getFormatter().formatNumber(this.value);
    }

    /**
     * Constructs new instance of ByteableNumber and init size and value fields
     * with passed values.
     * @param type type of the number.
     * @param size size of the number in bytes.
     * @param value value of ByteableNumber as long.
     * @throws IllegalArgumentException if size is unsupported; if value is out
     * of range.
     */
    protected ByteableNumber(final TypeInstantiable type, final int size, final long value) {
        this(type, size, value, null, false);
    }

    /**
     * Constructs new instance of ByteableNumber, init size value with passed
     * value and reads value field from passed byte buffer.
     * @param type type of the number.
     * @param size size of the number in bytes.
     * @param buffer source byte buffer.
     * @throws NullPointerException if buffer parameter is null.
     * @throws IllegalArgumentException if size is unsupported; if value
     * cannot be read from byte buffer; if value is out of range.
     */
    protected ByteableNumber(final TypeInstantiable type, final int size, final ByteBuffer buffer) {
        this(type, size, 0, buffer, true);
    }

    /**
     * Returns String representation of the ByteableNumber.
     * @return String representation.
     */
    public final String toString() {
        return cachedString;
    }

    /**
     * Returns value of ByteableNumber as byte.
     * @return value as byte.
     */
    public final byte byteValue() {
        return (byte) value;
    }

    /**
     * Returns value of ByteableNumber as short.
     * @return value as short.
     */
    public final short shortValue() {
        return (short) value;
    }

    /**
     * Returns value of ByteableNumber as int.
     * @return value as int.
     */
    public final int intValue() {
        return (int) value;
    }

    /**
     * Returns value of ByteableNumber as long.
     * @return value as long.
     */
    public final long longValue() {
        return value;
    }

    /**
     * Compares this ByteableNumber with another ByteableNumber.
     * @param o another ByteableNumber
     * @return -1, 0 or 1.
     */
    public final int compareTo(final Object o) {//removed final idetifier Pavlov Dmitry dpavlov@meshnetics.com
        long thisVal = this.value;
        long anotherVal = ((ByteableNumber) o).value;
        return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));
    }

    /**
     * Equals method realization.
     * @param o another ByteableNumber.
     * @return true if numbers are equal.
     */
    public final boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final ByteableNumber that = (ByteableNumber) o;
        return size == that.size && value == that.value;
    }

    /**
     * Returns hash code of ByteableNumber.
     * @return hash code.
     */
    public final int hashCode() {
        return cachedHash;
    }

    /**
     * Returns size of ByteableNumber in bytes.
     * @return size in bytes.
     */
    public final int getDataSize() {
        return size;
    }

    /**
     * Stores ByteableNumber to byte buffer. Checks if buffer has enough
     * space before storing.
     * @param buffer target byte buffer.
     * @throws NullPointerException if buffer parameter is null.
     * @throws IllegalArgumentException if buffer doesn't have enough
     * space.
     */
    public final void serialize(ByteBuffer buffer) {
        Utils.checkBuffer(buffer, size);
        writeBytes(value, buffer);
    }

    /**
     * Returns a value restored from byte buffer. <b>Note:</b> When this method
     * is called buffer parameter is already checked for null and checked if
     * buffer has enough data bytes.
     * @param buffer source buffer.
     * @return value as long.
     */
    protected abstract long readBytes(final ByteBuffer buffer);

    /**
     * Stores value of ByteableNumber (passed as long) to byte byffer. <b>Note:
     * </b> When this method is called buffer parameter is already checked for
     * null and checked if buffer has enough data bytes.
     * @param value value of ByteableNumber as long.
     * @param buffer target byte buffer.
     */
    protected abstract void writeBytes(final long value, final ByteBuffer buffer);

    /**
     * Returns range checker for ByteableNumber. Method called after value is
     * assigned from passed parameter or restored from byte buffer.
     * @return range checker.
     */
    protected abstract RangeCheck getRangeCheck();

    /**
     * Returns ByteableNumber formatter. Used in toString method for String
     * representation of ByteableNumber.
     * @return number formatter.
     */
    protected abstract NumberFormatter getFormatter();
}
